package org.tang.hool.bean;

import org.tang.hool.annotation.Controller;
import org.tang.hool.annotation.Service;
import org.tang.hool.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 用户查找、读取配置bean文件
 *
 * @author Tang Jiujia
 * @since 2022/5/5
 */
public class BeanDefinitionReader {

    private List<String> beanClassNames = new ArrayList<>();
    private Properties properties = new Properties();
    private final String SCAN_PACKAGE = "scanPackage";

    public BeanDefinitionReader(String... locations) {
        ClassLoader classLoader = this.getClass().getClassLoader();
        String location = locations[0].replace("classpath:", "");
        try (InputStream is = classLoader.getResourceAsStream(location)) {
            properties.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        }
        registerClasses(properties.getProperty(SCAN_PACKAGE));
    }

    public Properties getConfig() {
        return this.properties;
    }

    /**
     * 将扫描到的所有类信息转换为BeanDefinition
     *
     * @return BeanDefinition list
     * @author Tang Jiujia
     * @since 2022/5/5
     */
    public List<BeanDefinition> loadBeanDefinitions() {
        List<BeanDefinition> definitionList = new ArrayList<>(beanClassNames.size());
        for (String className : beanClassNames) {
            Class cls = null;
            try {
                cls = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (cls.isInterface()) continue;
            String factoryBeanName = StringUtil.toLowerFirstCase(cls.getSimpleName());
            BeanDefinition beanDefinition = createBeanDefinition(factoryBeanName, cls.getName());
            definitionList.add(beanDefinition);
            Class[] interfaces = cls.getInterfaces();
            for (Class<?> clsInterface : interfaces) {
                BeanDefinition definition = createBeanDefinition(clsInterface.getName(), cls.getName());
                definitionList.add(definition);
            }
        }
        return definitionList;
    }

    /**
     * 检索扫描路径，把路径下的对象className存入list
     *
     * @param scanPackage 扫描路径
     * @author Tang Jiujia
     * @since 2022/5/5
     */
    private void registerClasses(String scanPackage) {
        String path = scanPackage.replace(".", "/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> urls = null;
        try {
            urls = classLoader.getResources(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)) {
                        String packagePath = url.getPath().replaceAll("%20", " ");
                        try {
                            addClasses(packagePath, scanPackage);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    } else if ("jar".equals(protocol)) {
                        JarURLConnection jarConn;
                        try {
                            jarConn = (JarURLConnection) url.openConnection();
                            if (jarConn != null) {
                                JarFile jarFile = jarConn.getJarFile();
                                if (jarFile != null) {
                                    Enumeration<JarEntry> entries = jarFile.entries();
                                    while (entries.hasMoreElements()) {
                                        JarEntry jarEntry = entries.nextElement();
                                        String jarEntryName = jarEntry.getName();
                                        if (jarEntryName.endsWith(".class")) {
                                            String className = jarEntryName
                                                    .substring(0,jarEntryName.lastIndexOf("."))
                                                    .replaceAll("/", ".");
                                            Class<?> cls = Class.forName(className);
                                            boolean isAuto = cls.isAnnotationPresent(Controller.class)
                                                    || cls.isAnnotationPresent(Service.class);
                                            if (isAuto) {
                                                beanClassNames.add(className);
                                            }
                                        }
                                    }
                                }
                            }
                        } catch (IOException | ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private void addClasses(String packagePath, String packageName) throws ClassNotFoundException {
        File[] files = new File(packagePath).listFiles(file ->
                (file.isFile() && file.getName().endsWith(".class"))
                        || file.isDirectory());

        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile()) {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (packageName != null && !packageName.trim().equals("")) {
                    className = packageName + "." + className;
                }
                Class<?> cls = Class.forName(className);
                boolean isAuto = cls.isAnnotationPresent(Controller.class)
                        || cls.isAnnotationPresent(Service.class);
                if (isAuto) {
                    beanClassNames.add(className);
                }
            } else {
                String subPackagePath = fileName;
                if (packagePath != null && !packagePath.trim().equals("")) {
                    subPackagePath = packagePath + subPackagePath + "/";
                }
                String subPackageName = fileName;
                if (packageName != null && !packageName.trim().equals("")) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClasses(subPackagePath, subPackageName);
            }
        }
    }

    /**
     * 解析配置信息为BeanDefinition
     *
     * @author Tang Jiujia
     * @since 2022/5/5
     */
    private BeanDefinition createBeanDefinition(String factoryBeanName, String beanClassName) {
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setFactoryBeanName(factoryBeanName);
        beanDefinition.setBeanClassName(beanClassName);
        return beanDefinition;
    }
}

